package cn.amossun.starter.web.data.security.handler;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import cn.amossun.starter.common.util.Page;
import cn.amossun.starter.web.data.security.IDataSecurityConfig;
import cn.amossun.starter.web.data.security.IDataSecurityVersion;
import cn.amossun.starter.web.data.security.entity.DataSecurityConfigBO;
import cn.amossun.starter.web.data.security.entity.DataSecurityConfigDTO;
import cn.amossun.starter.web.data.security.entity.DataSecurityVersionBO;
import cn.amossun.starter.mybatis.data.security.properties.ResultConfiguration;
import cn.amossun.starter.mybatis.data.security.properties.ResultMapConfiguration;
import cn.amossun.starter.redis.cache.exception.RedisCacheConfigException;
import cn.amossun.starter.redis.cache.exception.RedisCacheConfigNotFoundException;
import cn.amossun.starter.redis.cache.exception.RedisRelatedException;
import cn.amossun.starter.redis.cache.properties.MultiRedisProperties;
import com.google.common.collect.Lists;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @program: BladeX-Boot
 * @description:
 * @author: Amos.Sun
 * @DateTime: 2020/03/08 18:16
 **/
@Data
@Slf4j
public class DataSecurityConfigHandler implements IDataSecurityConfig {

	public final RedisTemplate amosMultiRedisTemplate;

	public final IDataSecurityVersion dataSecurityVersion;

	private final MultiRedisProperties multiRedisProperties;

	public DataSecurityConfigHandler(@Autowired(required = false)
									 @Qualifier("amosMultiRedisTemplate") RedisTemplate amosMultiRedisTemplate,
									 IDataSecurityVersion dataSecurityVersion,
									 MultiRedisProperties multiRedisProperties) {
		this.amosMultiRedisTemplate = amosMultiRedisTemplate;
		this.dataSecurityVersion = dataSecurityVersion;
		this.multiRedisProperties = multiRedisProperties;
	}

	@Override
	public List<ResultMapConfiguration> getAllConfigs(String serviceName) throws RedisRelatedException, RedisCacheConfigException {
		assertService(serviceName);

		Object object = amosMultiRedisTemplate.opsForValue().get(getSecurityConfigKey(serviceName));
		if(object == null) {
			log.info("获取" + serviceName + " redis加解密配置json为空.");
			return Lists.newArrayList();
		}

		JSONArray jsonArray = JSONUtil.parseArray(JSONUtil.toJsonStr(object));
		List<ResultMapConfiguration> ormConfigs = JSONUtil.toList(jsonArray, ResultMapConfiguration.class);
		if (CollectionUtil.isEmpty(ormConfigs)) {
			return Lists.newArrayList();
		}
		log.info("获取" + serviceName + " redis加解密配置json {} 个.", ormConfigs.size());
		return ormConfigs;
	}

	protected List<ResultMapConfiguration> getOriginConfigs(String serviceName, boolean isReverse) throws RedisCacheConfigException, RedisRelatedException {
		List<ResultMapConfiguration> ormConfigs = getAllConfigs(serviceName);
		if(isReverse) {
			Collections.reverse(ormConfigs);
		}
		return ormConfigs;
	}

	@Override
	public Page<DataSecurityConfigBO> getOriginConfigs(String serviceName, int current, int size, String tableName) throws RedisRelatedException, RedisCacheConfigException {
		assertService(serviceName);

		Page page = new Page();
		page.setCurrent(current);
		page.setSize(size);

		//获取数据
		List<ResultMapConfiguration> ormConfigs = getOriginConfigs(serviceName, true);
		if(CollectionUtil.isEmpty(ormConfigs)) {
			return page;
		}

		List<DataSecurityConfigBO> records = convertToBO(serviceName, ormConfigs);

		//条件过滤
		if(StrUtil.isNotEmpty(tableName) && StrUtil.isNotEmpty(tableName.trim())) {
			List result = records.stream().filter(source-> source.getTableName().contains(tableName.trim())).collect(Collectors.toList());
			records = result;
		}

		if (CollectionUtil.isEmpty(records)) {
			return page;
		}

		//结果内存分页
		page.computeRecords(records);
		return page;
	}

	@Override
	public boolean checkConfig(String serviceName, List<ResultMapConfiguration> resultMapConfigurations) throws RedisCacheConfigNotFoundException, RedisCacheConfigException {
		if (CollectionUtil.isEmpty(resultMapConfigurations)) {
			throw new RedisCacheConfigException("安全配置项不允许为空.");
		}

		//唯一性校验tableName + objectName
		Set<String> onlyConfigSet = new HashSet<>(resultMapConfigurations.size());
		for(ResultMapConfiguration source : resultMapConfigurations) {
			String tableNameObjectName = new StringBuilder("表名:").append(source.getTableName()).append(",类名:").append(source.getObjectClassName()).toString();
			if(onlyConfigSet.contains(tableNameObjectName)) {
				throw new RedisCacheConfigException("安全配置项存在重复,请检查" + tableNameObjectName);
			}
			onlyConfigSet.add(tableNameObjectName);
		}


		for(ResultMapConfiguration source : resultMapConfigurations) {
			Map<String, Boolean> tableFiledEncryptMap = new HashMap<>(source.getResults().size());//逻辑校验 key(tableName + filedName + 加密) + boolean
			for(ResultConfiguration source2 : source.getResults()) {
				String tableNameFiledName = new StringBuilder("表名:").append(source.getTableName()).append(",表字段:").append(source2.getColumn()).toString();
				if(tableFiledEncryptMap.containsKey(tableNameFiledName) && source2.isEncrypt() != tableFiledEncryptMap.get(tableNameFiledName)) {
					throw new RedisCacheConfigException("加密安全配置项存在重复,请检查表名&表字段:" + tableNameFiledName);
				}
				tableFiledEncryptMap.put(tableNameFiledName, source2.isEncrypt());
			}
		}


		for(ResultMapConfiguration source : resultMapConfigurations) {
			Map<String, Boolean> objectPropertyDecryptMap = new HashMap<>(source.getResults().size());//逻辑校验 key(objectName + property + 解密) + boolean
			for(ResultConfiguration source2 : source.getResults()) {
				if(StrUtil.isEmpty(source.getObjectClassName()) && StrUtil.isNotEmpty(source2.getProperty())) {
					throw new RedisCacheConfigException("解密安全配置项存在属性名,请配置对应类名.");
				}
				if(StrUtil.isEmpty(source2.getProperty())) {
					continue;
				}
				String objectPropertyName = new StringBuilder("类名:").append(source.getObjectClassName()).append(",属性名:").append(source2.getProperty()).toString();
				if(objectPropertyDecryptMap.containsKey(objectPropertyName) && source2.isDecrypt() != objectPropertyDecryptMap.get(objectPropertyName)) {
					throw new RedisCacheConfigException("解密安全配置项存在重复,请检查类名&属性名: " + objectPropertyName);
				}
				objectPropertyDecryptMap.put(objectPropertyName, source2.isDecrypt());
			}
		}

		return true;
	}

	@Override
	public boolean save(DataSecurityConfigDTO param) throws RedisRelatedException, RedisCacheConfigException, RedisCacheConfigNotFoundException {
		assertService(param.getServiceName());

		//获取所选服务安全配置
		List<ResultMapConfiguration> ormConfigs = getOriginConfigs(param.getServiceName(), false);
		ormConfigs.add(BeanUtil.copyProperties(param, ResultMapConfiguration.class));

		//校验数据安全配置-逻辑合理性
		checkConfig(param.getServiceName(), ormConfigs);

		//保存更新版本记录
		dataSecurityVersion.addRecords(param.getUserId(), param.getServiceName(), ormConfigs);
		//保存最新数据安全配置
		patchOrmConfigs(param.getServiceName(), ormConfigs);
		return true;
	}

	@Override
	public boolean update(DataSecurityConfigDTO param) throws RedisRelatedException, RedisCacheConfigException, RedisCacheConfigNotFoundException {
		assertService(param.getServiceName());
		if(CollectionUtil.isEmpty(param.getResults())) {
			throw new RedisCacheConfigException("更新安全配置项不允许为空.");
		}
		int updateIndex = param.getIndex();
		//获取所选服务安全配置
		List<ResultMapConfiguration> ormConfigs = getOriginConfigs(param.getServiceName(), true);
		checkData(updateIndex, ormConfigs);
		ormConfigs.set(updateIndex - 1, BeanUtil.copyProperties(param, ResultMapConfiguration.class));

		//校验数据安全配置-逻辑合理性
		checkConfig(param.getServiceName(), ormConfigs);

		Collections.reverse(ormConfigs); //存储数据时恢复未列表原始未排序数据(实际存储顺序)
		//保存更新版本记录
		dataSecurityVersion.addRecords(param.getUserId(), param.getServiceName(), ormConfigs);
		//保存最新数据安全配置
		patchOrmConfigs(param.getServiceName(), ormConfigs);
		return true;
	}

	@Override
	public boolean remove(DataSecurityConfigDTO param) throws RedisRelatedException, RedisCacheConfigException {
		assertService(param.getServiceName());

		int removeIndex = param.getIndex();
		//获取所选服务安全配置
		List<ResultMapConfiguration> ormConfigs = getOriginConfigs(param.getServiceName(), true);
		checkData(removeIndex, ormConfigs);
		ormConfigs.remove(removeIndex -1);

		Collections.reverse(ormConfigs); //存储数据时恢复未列表原始未排序数据(实际存储顺序)
		//保存更新版本记录
		dataSecurityVersion.addRecords(param.getUserId(), param.getServiceName(), ormConfigs);
		//保存最新数据安全配置
		patchOrmConfigs(param.getServiceName(), ormConfigs);
		return true;
	}

	private void checkData(int dataIndex, List ormConfigs) throws RedisCacheConfigException {
		if(ObjectUtil.isEmpty(dataIndex)) {
			throw new RedisCacheConfigException("序号为空,请刷新后重试.");
		}
		if(CollectionUtil.isEmpty(ormConfigs)) {
			throw new RedisCacheConfigException("数据为空,请刷新后重试.");
		}
		if(dataIndex-1 >= ormConfigs.size()) {
			throw new RedisCacheConfigException("数据已更新,请刷新后重试.");
		}
	}

	private List<DataSecurityConfigBO> convertToBO(String serviceName, List<ResultMapConfiguration> result) {
		if (CollectionUtil.isEmpty(result)) {
			return null;
		}
		List<DataSecurityConfigBO> records = BeanUtil.copyToList(result, DataSecurityConfigBO.class);
		int index = 1;
		for(DataSecurityConfigBO source : records) {
			source.setIndex(index);
			source.setServiceName(serviceName);
			++index;
		}
		return records;
	}

	protected boolean patchOrmConfigs(String serviceName, List<ResultMapConfiguration> ormConfigs) throws RedisRelatedException, RedisCacheConfigException {
		assertService(serviceName);

		amosMultiRedisTemplate.opsForValue().set(getSecurityConfigKey(serviceName), JSONUtil.toJsonStr(ormConfigs));
		return true;
	}

	@Override
	public boolean rollback(String userId, String serviceName, String version) throws RedisCacheConfigException, RedisRelatedException {
		assertService(serviceName);
		//查询当前版本
		//判断是否为最新版本.
		if(dataSecurityVersion.isLastVersion(serviceName, version)) {
			throw new RedisCacheConfigException("当前已是最新版本.");
		}
		DataSecurityVersionBO dataSecurityVersionBO = dataSecurityVersion.get(serviceName, version);
		if(ObjectUtil.isNull(dataSecurityVersionBO)) {
			log.warn("回滚版本不存在,请重新选择.");
			return false;
		}
		List<ResultMapConfiguration> ormConfigs = null;
		if(ObjectUtil.isNotNull(dataSecurityVersionBO) && StrUtil.isNotEmpty(dataSecurityVersionBO.getVersionValue())) {
			JSONArray jsonArray = JSONUtil.parseArray(JSONUtil.toJsonStr(dataSecurityVersionBO.getVersionValue()));
			ormConfigs = JSONUtil.toList(jsonArray, ResultMapConfiguration.class);
		}
		//保存更新版本记录
		dataSecurityVersion.addRecords(userId, serviceName, ormConfigs);
		//保存最新数据安全配置
		patchOrmConfigs(serviceName, ormConfigs);

		return true;
	}

	@Override
	public List<String> getDynamicRedis() {
		if(ObjectUtil.isEmpty(multiRedisProperties) || CollectionUtil.isEmpty(multiRedisProperties.getDynamic())) {
			return new ArrayList<>(0);
		}
		return multiRedisProperties.getDynamic().keySet().stream().collect(Collectors.toList());
	}

}
